<template>
  <el-col :span="24" v-if="(hasSlot(`${item.field}Header`) || item.headerRender) && !isHiddenItem(item) && ifData">
    <slot :name="`${item.field}Header`" :backData="{ item, data: data[`${item.field}`] }" :isEditByRow="isEditByRow">
      <span v-if="item.headerRender">
        <component v-if="(typeof item.headerRender !== 'string')" :is="item.headerRender" :item="item" :data="data[`${item.field}`]"></component>
        <span v-else v-html="item.headerRender"></span>
      </span>
    </slot>
  </el-col>
  <el-col :key="item.field" v-bind="{ ...(getLayout(item, colLayout) ?? {}) }" :class="`${item.field}Col`"
    v-if="!isHiddenItem(item) && ifData" v-show="showData">
    <el-form-item :ref="(el: any) => setFormItemRef(el, item.field)" class="form-item" :class="`${item.field}Class`"
      v-show="!item.isHidden"
      :style="{
        ...itemStyle,
        ...((hasSlot(`${item.field}Header`) || item.headerRender) ? {padding: '0', 'margin-bottom': '0px'} : {}),
        ...(item.style ?? {})
      }"
      :prop="propKeyCom"
      v-bind="{...item.formItemConfig, ...(rulesData ? {rules: rulesData} : {})}">
      <template #label v-if="!item.hideLabel">
        <slot :name="item.field + 'CustomLabel'" :backData="item" :isEditByRow="isEditByRow">
          <el-tooltip v-if="item.tip" :content="item.tip" v-bind="item.tipConfig">
            <el-icon>
              <QuestionFilled />
            </el-icon>
          </el-tooltip>
          <NEllipsis v-bind="{
            'line-clamp': 2,
            style: {
              'line-height': '16px'
            },
            ...(item.labelEllipsis ?? {})
          }" >
            <span v-if="typeof labelRefData === 'string'" v-html='labelRefData'></span>
            <component v-else :is="labelRefData"></component>
          </NEllipsis>
        </slot>
      </template>
      <slot :name="`${item.field}Before`" :backData="{ item, data: data[`${item.field}`] }" :isEditByRow="isEditByRow"></slot>
      <template v-if="item.type">
        <component
          :is="componentIs">
        <template
          v-for="slotName in item.slotNames"
          #[slotName]="slotData"
        >
          <slot
            :name="`${item.field}` + capitalizeFirstLetter(slotName)"
            :backData="{
              ...slotData,
              item,
              dataValue: data[`${item.field}`],
              formData: data,
            }"
            :isEditByRow="isEditByRow"
          >
          </slot>
        </template>
        </component>
      </template>
      <slot :name="`${item.field}After`" :backData="{ item, data: data[`${item.field}`] }" :isEditByRow="isEditByRow"></slot>
    </el-form-item>
  </el-col>
</template>
<script setup lang='ts'>
import { isEqual } from 'lodash-es'
import getLayout from './config/layout.ts'
import { Props as PropsType, FormItem, allDataType , watchFormApi} from './types/index.ts'
import { useDependencies, isVueComponentOrVNode, useHandleChangeData } from './utils/index'
import { componentsMap } from './cpn/index.ts'
import { NEllipsis } from 'naive-ui'
import { onMounted, reactive, watch, watchEffect } from 'vue'
const slots = defineSlots()

type propMsg = Pick<
  PropsType,
  'itemStyle' | 'data' | 'allDisabled' | 'propKey' | 'hideItems' | 'colLayout'
> & {
  isEditByRow?: boolean
  itemData: FormItem
  allData: allDataType
  value: PropsType['data']
  WeakMapData?: WeakMap<object, allDataType>
  slotInfo?: Record<string, any>
  tmpInfo?: any
}
const emits = defineEmits(['keyUpEnter', 'update:value'])
const props = withDefaults(defineProps<propMsg>(), {
  isEditByRow: true,
  itemStyle: () => ({}),
  data: () => ({}),
  allDisabled: false,
  hideItems: () => [],
  slotInfo: () => ({}),
  tmpInfo: () => ({}),
})

const {
  colLayout,
  propKey,
  itemData: item,
  itemStyle,
  allDisabled,
  allData,
  value: data,
} = toRefs(props)


console.log('item>>>>>>>>>>>>>', allData);



const isEditByRow = ref<boolean>(true)
watchEffect(() => {
  isEditByRow.value = item.value.isEdit ?? props.isEditByRow ?? true
})

const labelRefData = ref<string | Component>('')
watchEffect(() => { labelRefData.value = item.value.label ?? '' })




provide('formItemProvideTable' + item.value.field, {isEditByRow: isEditByRow})

const configProps = ref({}) // 动态组件参数只能放在FieldComponentH上面，因为会被用到
// 找到是哪个组件
const FieldComponent = computed(() => {
  const finalComponent =  typeof ((item.value as FormItem).type) === 'string'
    ? componentsMap[(item.value as FormItem).type]
    : (item.value as FormItem).type;
  if (!finalComponent) {
    // 组件未注册
    console.warn(`Component ${(item.value as FormItem).type} is not registered`);
  }
  return finalComponent;
});
const [specificChangeData] = allData.value ? useHandleChangeData(allData.value.allChangeData, item, data) : [() => {}]
// 通过组件进行传参数
const FieldComponentH = defineComponent({
  setup(_props, {slots: slotNodes}) {
    return () => {
      return h(
        FieldComponent.value ?? 'span',
        {
          getRef: (el: any) => setItemRef(el, item.value.field),
          isEditByRow: isEditByRow.value,
          allDisabled: allDisabled.value ?? disabledData.value,
          onSpecific: specificChangeData,
          formItemRef: formItemRef.value,
          item: {
            ...item.value,
            config:{
              ...item.value.config,
              ...(configProps?.value ? { ...configProps.value } : {})
            }
          },
          value: data.value?.[`${item.value.field}`],
          'onUpdate:value': updateValue,
          data: data.value,
          'onUpdate:data': updateData,
          onKeyUpEnter: ($event: any) => keyUpEnter($event, item)
        },
        slotNodes
      )
    }
    
  }
})

const componentIs = defineComponent({
  setup(_props, {slots}) {
    if(item.value.dependencies?.render) {
      return () => toValue(renderExample)
    }
    if(item.value?.render) {
      const renderReturn = (item.value.render as any)(readonly(data.value ?? {}), {component: markRaw(FieldComponentH),})
      if(isVueComponentOrVNode(renderReturn)) {
        return () => renderReturn
      }
      return () => h('span', {
        innerHTML: typeof renderReturn === 'string' ? renderReturn : JSON.stringify(renderReturn)
      })
    }
    return () => h(FieldComponentH, {}, slots)
  }
})

const setItemRef = (el: any | null, type: string) => {
  if (el) {
    allData.value.allRefs[type] = el
  }
}
const formItemRef = ref<HTMLElement | null>(null)
const setFormItemRef = (el: any | null, type: string) => {
  formItemRef.value = el
  if (el && allData.value) {
    allData.value.allFormItemRefs[type] = el
  }
}
if(allData.value.tableInfo) {
  allData.value.tableInfo.slotInfo = props.slotInfo
}

const keyUpEnter = ($event: any, current: any) => {
  emits('keyUpEnter', {
    event: $event,
    current,
  })
}

const capitalizeFirstLetter = (str: string) => {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

const propKeyCom = computed(() => {
  let arr = []
  if(propKey.value) {
    if(typeof propKey.value === 'string') {
      arr = [propKey.value]
    } else if(typeof propKey.value === 'number') {
      arr = [propKey.value.toString()]
    } else if(Array.isArray(propKey.value)) {
      arr = propKey.value as any[]
    }
  }
  return [
    ...arr,
    item.value.field,
  ]
})

const hasSlot = (slotName: string) => {
  return !!slots[slotName]
}

const isHiddenItem = (item: FormItem) => {
  if (item.isHidden) {
    return true
  }
  let flag = false
  if (isRef(props.hideItems)) {
    if (props.hideItems.value.includes(item.field)) {
      flag = true
    }
  } else if (Array.isArray(props.hideItems)) {
    if (props.hideItems.includes(item.field)) {
      flag = true
    }
  }
  return flag
}

onMounted(() => { // 默认值
  if(!Object.prototype.hasOwnProperty.call(data.value, item.value.field) && Object.prototype.hasOwnProperty.call(item.value, 'defaultValue')) {
    coreValue.value = item.value.defaultValue
  }
})

const isEvent = ref<boolean>(false)
function updateValue(value: any, isEventArg: boolean = true) {
  emits('update:value', { ...data.value, [item.value.field]: value })
  if(isEventArg && allData.value) {
    isEvent.value = true
    allData.value.allEvent[item.value.field] = true
    nextTick(() => { // 为了兼容table的行内编辑
      nextTick(() => {
        isEvent.value = false
        allData.value.allEvent[item.value.field] = false
      })
    })
  }
}
function updateData(rowDataValue: Record<string, any>, _isEventArg: boolean = true) {
  emits('update:value', { ...data.value, ...rowDataValue })
}
const coreValue = computed({
  get() {
    return data.value?.[`${item.value.field}`]
  },
  set(value) {
    updateValue(value, false)
  }
})


const { ifData, showData, disabledData, rulesData, renderExample } =
  useDependencies(item, data, {
    configProps,
    component: markRaw(FieldComponentH),
    allData: allData.value,
    propKey: propKeyCom,
    isEvent,
    coreValue, // 可以直接修改的value值
    WeakMapData: props.WeakMapData, // TODO: 还没想好怎么做
    isEditByRow,
    tmpInfo: props.tmpInfo // 临时值,用于作嵌套时候增加作用域
  })

  onMounted(() => {
    // 可以修改的
    const adaptable = {
      if: ifData,
      show: showData,
      disabled: disabledData,
      rules: rulesData,
      render: renderExample,
      configProps: configProps,
      value: coreValue,
      isEdit: isEditByRow,
      label: labelRefData,
    }
    // 只读的
    const readonlyAdaptable: watchFormApi = {
      // ...adaptable,
      component: FieldComponentH,
      allData: allData.value,
      propKey: propKeyCom,
      coreValue, // 可以直接修改的value值
      WeakMapData: props.WeakMapData, // TODO: 还没想好怎么做
      isEditByRow,
      tmpInfo: props.tmpInfo, // 临时值,用于作嵌套时候增加作用域
      allEvent: allData.value.allEvent,
      allFormItemCoreRefs: allData.value.allRefs,
      allFormItemRefs: allData.value.allFormItemRefs,
      formItemRef: computed(() => {
        return allData.value?.allFormItemRefs?.[item.value.field]
      }),
      formItemCoreRef: computed(() => {
        return allData.value?.allRefs?.[item.value.field]
      }),
      tableInfo: allData.value.tableInfo ?? {},
      changeData: allData.value.allChangeData[item.value.field],
      allChangeData: allData.value.allChangeData,
      itemAt: item,
      rowData: data,
      watch: (one, two, tree = {}) => {
        watch(one, (newValue, oldValue) => {
          if(newValue === oldValue) return
          if (isEqual(newValue, oldValue)) return
          if (typeof two === 'function') {
            two(newValue, oldValue)
          }
        }, tree)
      },

    }
    if (item.value.watch) {
      item.value.watch(reactive(adaptable), readonly(readonlyAdaptable))
    }
  })

</script>
<style scoped lang='scss'>
:deep(.el-form-item__label) {
  height: auto;
  align-items: center;
}
</style>